from querier.esquerier import ElasticSearchQuerier
from utils import utils
# import math
from querier.wechat.article_get_keywords import WechatGetKeywordsQuerier
from utils.wechat_article_dedup import article_deduplicate

MAX_LEN = 50
CATEGORY_CUTOFF = 0.1

MINIMUM_SHOULD_MATCH = '5<85% 10<9'
MAX_CHARACTER = 5


# todo use database for bigdata
class WechatRecommendQuerier(ElasticSearchQuerier):
    def __init__(self, es, index, doc_type, index_selected, doc_type_selected, nlp_service):
        super(WechatRecommendQuerier, self).__init__(None, None, None)
        self.getKeywordsQuerier = WechatGetKeywordsQuerier(es, index, doc_type)
        self.articleMatchQuerier = WechatArticleMatchQuerier(es, index_selected, doc_type_selected)
        self.nlp_service = nlp_service

    def search(self, args):
        ids = args.get('ids', None)
        if not ids:
            ids = []

        # ids = ids[0:30]
        push_ids = args.get('push_ids', None)
        if not push_ids:
            push_ids = []

        # push_ids = push_ids[0: 200]
        size = args.get('size', 12)
        if not size:
            size = 12
        from_ = args.get('from', 0)
        if not size:
            from_ = 0

        filters = args.get('filters', None)
        if not filters:
            filters = {}

        filters_not = args.get('filters_not', None)
        if not filters_not:
            filters_not = {'biz_code': ['gh_79987bf37777', 'gh_092d26981eb9', 'gh_921722ba095c', 'gh_f1abb8546dfe']}

        # if not ids:
        #     filters['selected'] = ['t']

        res = self.getKeywordsQuerier.search({"ids": ids})
        category = res.get('category', [])
        keywords = res.get('keywords', [])

        out = self.articleMatchQuerier.search(
                {'push_ids': list(set(push_ids + ids)), 'keywords': keywords[0: MAX_LEN], 'category': category,
                 'filters': filters, 'filters_not': filters_not, "size": size, 'from': from_})
        return out

    def _build_query(self, args):
        pass

    def _build_result(self, es_result, param):
        pass


class WechatArticleMatchQuerier(ElasticSearchQuerier):
    def __init__(self, es, index, doc_type):
        super(WechatArticleMatchQuerier, self).__init__(es, index, doc_type)

    def _build_query(self, args):
        keywords = args.get('keywords', None)
        if not keywords:
            keywords = []

        filters = args.get('filters', {})
        if not filters:
            filters = {}

        filters['from'] = [15]  # only for weibo toutiao
        size = args.get('size', 12)
        if not size:
            size = 12
        from_ = args.get('from', 0)
        if not size:
            from_ = 0

        ids = args.get('push_ids', None)
        if not ids:
            ids = []

        filters_not = args.get('filters_not', None)

        query = self._gen_query(keywords, filters, filters_not, ids, from_, size)
        return query, {}, {'keywords': keywords, "filters": filters, 'filters_not': filters_not}

    def _build_result(self, es_result, param):
        keywords = param['keywords']
        keywords.sort()
        total = es_result['hits']['total']
        articles = []
        for hit in es_result['hits']['hits']:
            articles.append(extract_result(hit))

        ids = [{'article_id': a['article_id'], 'publish_timestamp': a['publish_timestamp']} for a in articles]

        try:
            articles = article_deduplicate(articles, 128, 10)
        except Exception as e:
            print(str(e))
            pass

        return {
            'total': total,
            'filters': param['filters'],
            'keywords': keywords,
            'articles': articles,
            'ids': ids
        }

    def _gen_query(self, keywords, filters, filters_not, ids, from_, size_):
        must_clause = []
        should_clause = []
        filter_clause = []

        if filters:
            # filter_clause = self._add_filter_clause(filter_clause, filters, 'biz_code', 'should')
            filter_clause = self._add_filter_match(filter_clause, filters, 'biz_code', 'should')
            filter_clause = self._add_filter_match(filter_clause, filters, 'from', 'should')
            filter_clause = self._add_filter_clause(filter_clause, filters, 'has_copyright', 'should')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'publish_timestamp')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'read_num')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'like_num')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'like_read_ratio')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'image_num')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'video_num')
            filter_clause = self._add_filter_range_clause(filter_clause, filters, 'text_len')

            if filters.get('category'):
                filters['category'] = [utils.category_smzdm_2_encode(c) for c in filters['category']]
                filters['category_weight'] = [CATEGORY_CUTOFF]
                filter_clause = self._add_filter_clause(filter_clause, filters, 'category', 'should')
                filter_clause = self._add_filter_range_clause(filter_clause, filters, 'category_weight')

        if filters_not:
            filter_clause = self._add_filter_match(filter_clause, filters_not, 'biz_code', 'must_not')
            # filter_clause = self._add_filter_match(filter_clause, filters_not, 'article_id', 'must_not')

        if keywords:
            should_clause.append(
                {
                    'multi_match': {
                        'analyzer': 'whitespace',
                        'query': ' '.join(keywords),
                        'fields': ['keywords', 'title_seg^3'],
                    }
                }
            )

        must_not_clause = {
            "bool": {"must_not": {"terms": {"article_id": ids}}}
        }

        filter_clause.append(must_not_clause)

        query = {"query": {
            "bool": {
                "filter": filter_clause,
            }
        }, 'from': from_, 'size': size_}

        if must_clause:
            query['query']['bool']['must'] = must_clause

        if should_clause:
            query['query']['bool']['should'] = should_clause

        query['sort'] = [
            '_score',
            {'publish_timestamp': 'desc'}
        ]

        if filters.get('category'):
            query['sort'] = [{'category_weight': 'desc'}] + query['sort']

        return query

    @staticmethod
    def _add_filter_match(must_clause, filters, key, cond='must'):
        if key in filters:
            if filters[key]:
                clause = []
                must_clause.append({
                    'bool': {cond: clause}
                })
                values = filters[key]
                if isinstance(values, str):
                    values = values.split(' ')
                for fk in values:
                    clause.append({'match': {key: {'query': fk, 'minimum_should_match': '20<100% 20<20'}}})
        return must_clause

    @staticmethod
    def _add_filter_clause(filter_clause, filters, key, cond='must'):
        if key in filters:
            if filters[key]:
                clause = []
                filter_clause.append({
                    'bool': {
                        cond: clause
                    }
                })
                for fk in filters[key]:
                    clause.append({'term': {key: fk}})
        return filter_clause

    @staticmethod
    def _add_filter_range_clause(filter_clause, filters, key):
        if key in filters:
            if filters[key]:
                clause = []
                filter_clause.append({
                    'bool': {
                        'must': clause
                    }
                })
                fk = filters[key]
                if not isinstance(fk, list) or len(fk) < 1:
                    pass
                else:
                    min_fk = fk[0]
                    if len(fk) >= 2:
                        max_fk = fk[1]
                    else:
                        max_fk = None
                    if min_fk is not None and min_fk != 'null':
                        clause.append({'range': {key: {"gte": min_fk}}})
                    if max_fk is not None and max_fk != 'null':
                        clause.append({'range': {key: {"lte": max_fk}}})
        return filter_clause


def extract_result(hit):
    source_ = hit['_source']
    return {
        'title': utils.clean_text(source_['title']),
        'title_seg': source_.get('title_seg', []),
        'url': source_['url'],
        'id': source_['id'],
        'article_id': source_['article_id'],
        'biz_name': source_['biz_name'],
        'msg_cdn_url': source_['msg_cdn_url'],
        'read_num': source_['read_num'],
        'like_num': source_['like_num'],
        'publish_timestamp': source_['publish_timestamp'],
        'category': source_.get('category'),
    }
